package com.github.baichuan.mapping;

import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 对象属性自动映射器
 */
public class BeanMapper {
    /**
     * 将from对象的值映射到T类型的对象，只有配置Mapping Annotation的属性才会做映射
     *
     * @param from  映射数据来源对象
     * @param clazz 映射结果对象class
     * @param <F>   数据来源对象类型参数
     * @param <T>   T必须有无参的构造函数
     * @return
     */
    public static <F, T> T map(F from, Class<T> clazz) {
        return map(from, clazz, null);
    }

    public static <F, T> T map(F from, Class<T> clazz, Collection<String> properties) {
        try {
            T result = clazz.getConstructor().newInstance();
            List<FieldMapper> fieldMappers = getFieldMapper(clazz);

            for (FieldMapper mapper : fieldMappers) {
                Field field = mapper.getField();

                if(properties != null && !properties.contains(mapper.getField().getName()))
                    continue;

                Object value = mapper.map(from);
                field.set(result, value);
            }

            return result;
        } catch (Exception ex) {
            throw new MappingException(ex.getMessage(), ex);
        }
    }

    public static List<FieldMapper> getFieldMapper(Class<?> clazz){
        if (fieldMapperCache.containsKey(clazz)) {
            return fieldMapperCache.get(clazz);
        }

        Field[] fields = clazz.getDeclaredFields();
        List<FieldMapper> mappers = new ArrayList<>();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(Mapping.class)) {
                continue;
            }

            field.setAccessible(true);
            Mapping annotation = field.getAnnotation(Mapping.class);
            mappers.add(new FieldMapper(clazz, field, annotation));
        }
        fieldMapperCache.put(clazz, mappers);
        return mappers;
    }

    /**
     * 获取字段映射属性
     */
    public static String getMappingValue(Class<?> clazz, String fieldName) throws NoSuchFieldException {
        Field field = clazz.getDeclaredField(fieldName);
        Mapping mapping = field.getAnnotation(Mapping.class);
        if (StringUtils.isNotBlank(mapping.value())) {
            return mapping.value();
        }
        return field.getName();
    }


    /**
     * 类注解缓存
     */
    private static Map<Class<?>, List<FieldMapper>> fieldMapperCache = new ConcurrentHashMap<>();
}
